﻿using BCCommon;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace BCMongoData
{
    public class MongoDBCommand<T>
    {
        private IMongoClient client;
        private IMongoDatabase database;
        private IMongoCollection<T> collection;

        public MongoDBCommand(IMongoDBConfiguration mongoDBConfiguration, string collectionName)
        {
            if(!bool.TryParse(mongoDBConfiguration.MongodbClusterEnabled,out bool clusterEnabled)){
                this.client = new MongoClient(mongoDBConfiguration.ConnectionString);
                this.database = client.GetDatabase(mongoDBConfiguration.Database);
                this.collection = database.GetCollection<T>(collectionName);
                return;
            }
            if (!clusterEnabled)
            {
                this.client = new MongoClient(mongoDBConfiguration.ConnectionString);
                this.database = client.GetDatabase(mongoDBConfiguration.Database);
                this.collection = database.GetCollection<T>(collectionName);
                return;
            }
            else
            {
                List<MongoServerAddress> servers = new List<MongoServerAddress>();
                string reg = @"^(?'server'\d{1,}.\d{1,}.\d{1,}.\d{1,}):(?'port'\d{1,})$";
                string[] ServerList = mongoDBConfiguration.ServerAddress.Trim().Split('|');
                foreach (string server in ServerList)
                {
                    MatchCollection mc = Regex.Matches(server, reg);
                    if (mc != null && mc.Count > 0)
                        servers.Add(new MongoServerAddress(mc[0].Groups["server"].ToString(), Convert.ToInt32(mc[0].Groups["port"].ToString())));
                }

                if (servers == null || servers.Count < 1)
                    throw new Exception("Mongodb集群配置失败");

                MongoClientSettings set = new MongoClientSettings();

                set.Servers = servers;
                set.ReplicaSetName = mongoDBConfiguration.ReplicaSetName.Trim();//设置副本集名称

                int TimeOut = Convert.ToInt32(mongoDBConfiguration.TimeOut.Trim());//设置副本集名称

                set.ConnectTimeout = new TimeSpan(0, 0, 0, TimeOut, 0);//设置超时时间为5秒

                set.ReadPreference = new ReadPreference(ReadPreferenceMode.SecondaryPreferred);

                this.client = new MongoClient(set);

                this.database = client.GetDatabase(mongoDBConfiguration.Database);
                this.collection = database.GetCollection<T>(collectionName);
            }
        }
        public Task<IAsyncCursor<TResult>> Aggregate<TResult>(PipelineDefinition<T, TResult> pipelineDefinition)
        {
            return collection.AggregateAsync(pipelineDefinition);
        }

        public Task InsertDocumentAsync(T document)
        {
            return collection.InsertOneAsync(document);
        }

        public Task InsertDocumentAsync(IEnumerable<T> documents)
        {
            return collection.InsertManyAsync(documents);
        }

        public Task<DeleteResult> DeleteDocumentAsync(Expression<Func<T, bool>> func)
        {
            return collection.DeleteOneAsync(func);
        }

        public Task<DeleteResult> DeleteDocumentAsync(FilterDefinition<T> filter)
        {
            return collection.DeleteOneAsync(filter);
        }
        public Task<DeleteResult> DeleteDocumentsAsync(Expression<Func<T, bool>> func)
        {
            return collection.DeleteManyAsync(func);
        }

        public Task<DeleteResult> DeleteDocumentsAsync(FilterDefinition<T> filter)
        {
            return collection.DeleteManyAsync(filter);
        }
        public Task<T> UpdateDocumentAsync(Expression<Func<T, bool>> func, UpdateDefinition<T> update)
        {
            FindOneAndUpdateOptions<T> options = new FindOneAndUpdateOptions<T>
            {
                IsUpsert = true,
                ReturnDocument = ReturnDocument.After
            };

            return collection.FindOneAndUpdateAsync(func, update, options);
        }

        public Task<UpdateResult> UpdateDocumentsAsync(Expression<Func<T, bool>> func, UpdateDefinition<T> update)
        {
            return collection.UpdateManyAsync(func, update);
        }

        public Task<T> ReplaceDocumentAsync(Expression<Func<T, bool>> func, T document)
        {
            return collection.FindOneAndReplaceAsync(func, document);
        }


        public T ReplaceDocument(Expression<Func<T, bool>> func, T document)
        {
            return collection.FindOneAndReplace(func, document);
        }


        public Task<T> SelectDocumentAsync(Expression<Func<T, bool>> func)
        {
            return collection.Find(func).FirstOrDefaultAsync();
        }
        public Task<T> SelectDocumentAsync(Expression<Func<T, bool>> func, Expression<Func<T, object>> sortField, bool descending)
        {
            if (descending)
            {
                return collection.Find(func).SortByDescending(sortField).FirstOrDefaultAsync();
            }
            else
            {
                return collection.Find(func).SortBy(sortField).FirstOrDefaultAsync();
            }

        }
        public Task<T> SelectDocumentAsync(FilterDefinition<T> filter)
        {
            return collection.Find(filter).FirstOrDefaultAsync();
        }

        public Task<List<T>> SelectDocumentsAsync(Expression<Func<T, bool>> func)
        {
            return collection.Find(func).ToListAsync();
        }
        public Task<List<T>> SelectDocumentsAsync(FilterDefinition<T> filter)
        {
            return collection.Find(filter).ToListAsync();
        }
        public Task<List<TResult>> SelectDocumentsAsync<TResult>(FilterDefinition<T> filter, ProjectionDefinition<T, TResult> fields)
        {
            return collection.Find(filter).Project(fields).ToListAsync();
        }
        public Task<List<TResult>> SelectDocumentsAsync<TResult>(Expression<Func<T, bool>> func, ProjectionDefinition<T, TResult> fields)
        {
            return collection.Find(func).Project(fields).ToListAsync();
        }
        public async Task<PageableList<T>> SelectDocumentsAsync(Expression<Func<T, bool>> func, Expression<Func<T, object>> sortField, bool descending, int skip, int limit)
        {
            PageableList<T> pageableList = new PageableList<T>
            {
                Count = await collection.CountDocumentsAsync(func)
            };

            if (descending)
            {
                pageableList.Items = await collection.Find(func).SortByDescending(sortField).Skip(skip).Limit(limit).ToListAsync();
            }
            else
            {
                pageableList.Items = await collection.Find(func).SortBy(sortField).Skip(skip).Limit(limit).ToListAsync();
            }

            return pageableList;
        }

        public async Task<PageableList<T>> SelectDocumentsAsync(FilterDefinition<T> filter, Expression<Func<T, object>> sortField, bool descending, int skip, int limit)
        {
            PageableList<T> pageableList = new PageableList<T>
            {
                Count = await collection.CountDocumentsAsync(filter)
            };

            if (descending)
            {
                pageableList.Items = await collection.Find(filter).SortByDescending(sortField).Skip(skip).Limit(limit).ToListAsync();
            }
            else
            {
                pageableList.Items = await collection.Find(filter).SortBy(sortField).Skip(skip).Limit(limit).ToListAsync();
            }

            return pageableList;
        }
        public async Task<PageableList<TResult>> SelectDocumentsAsync<TResult>(Expression<Func<T, bool>> func, ProjectionDefinition<T, TResult> fields, Expression<Func<T, object>> sortField, bool descending, int skip, int limit)
        {
            PageableList<TResult> pageableList = new PageableList<TResult>
            {
                Count = await collection.CountDocumentsAsync(func)
            };

            if (descending)
            {
                pageableList.Items = await collection.Find(func).SortByDescending(sortField).Skip(skip).Limit(limit).Project(fields).ToListAsync();
            }
            else
            {
                pageableList.Items = await collection.Find(func).SortBy(sortField).Skip(skip).Limit(limit).Project(fields).ToListAsync();
            }

            return pageableList;
        }
        public Task<T> RunCommandAsync(Command<T> command)
        {
            return database.RunCommandAsync(command);
        }
    }
}
